package model;
import helper.Mail;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.GregorianCalendar;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.swing.Timer;


import controller.SessionControll;

/**
* Cron Job.
*
* @author Jonas Gutermuth
* @version 1.0 02/22/2011
*/
@SuppressWarnings("serial")
public class CronJob extends SessionControll implements ActionListener {
	/*
	 * sleep-intervalle unterschiedlicher länge (Bearbeiten ändert die Häufigkeit der Überprüfung nach durchzuführenden Aktionen)
	 * es gilt: intervall1 > intervall2 > intervall3 > 0
	 */
	private final int intervall1=5*60*1000, intervall2=30*1000, intervall3=100; // 5min, 30sec, 0,1sec  in Millisekunden
	/*
	 * zeiträume zwischen ereignissen
	 * dbIntervall= Pause zwischen Datenbankbereinigungen
	 * benachrichtungsPuffer= Zeit zwischen Erinnerung an kommenden Stichtag und Stichtag
	 * loeschAlter= Mindestalter einer Ebenenversion, dass diese Version permanent gelöscht wird
	 */
	private final long dbBerIntervall=5*3600*1000, benachrichtungsPuffer=10*24*3600*1000, loeschAlter=(long)2*365*24*3600*1000; // 5 Stunden, 10 Tage, 2 Jahre in Millisekunden
	
	// ab hier bitte nicht bearbeiten
	
	// ereigniszeiten
	private long stichtag=0, dbBereinugung=0, aufgabenbenachrichtigung=0, letzterDurchlauf=0;
	// der Timer initialisiert mit dem kürzeseten sleepintervall
	private Timer timer = new Timer(intervall3, this);
	
	/**
	 * Diese Funktion Initialisiert und Startet den Cronjob.
	 */
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		long jetzt;
		// -------------------------
		// erst alle stichtage ausführen die vergangen sind während der server aus war
		Stichtag[] s = stichtagRepository.toArray(new Stichtag[0]);
		if(s != null) {
			// sortieren
			if(s.length > 1) {
				Stichtag tmp;
				int min;
				for(int i=0; i<s.length-1; i++) {
					min = i;
					for(int j=i+1; j<s.length; j++) {
						if(s[j].getStichtag() < s[min].getStichtag()) {
							min = j;
						}
					}
					tmp = s[i];
					s[i] = s[min];
					s[min] = tmp;
				}
			}
			// Stichtage ausführen
			for(int i=0; i<s.length; i++) {
				jetzt = (new GregorianCalendar()).getTimeInMillis();
				if(s[i].getStatus()!=Stichtag.STICHTAGAUSGEFUEHRT && s[i].getStichtag() < jetzt) {
					stichtag(s[i].getStichtag(), jetzt);
				}
			}
		}
		// -------------------------
		// richtige initialisierung
		jetzt = (new GregorianCalendar()).getTimeInMillis();
		letzterDurchlauf = jetzt;
		// "dbBereinugung = jetzt+intervall1-dbBerIntervall" -> letzte reinigung war angeblich vor dbBerIntervall-intervall1 Zeiteinheiten -> nächste Bereinigung in intervall1 Zeiteiniheiten
		dbBereinugung = jetzt+intervall1-dbBerIntervall;
		aktualisiereZielzeiten(jetzt);
		// Timer starten
		timer.setRepeats(true);
		timer.start();
		System.out.println("Cronjob gestartet");
	}
	
	/**
	 * Diese Funktion wird wiederholt von einem Timer aufgerufen.
	 * Diese Funktion ruft die Funktionen aktualisiereZielzeiten(), stichtag(), dbBereinigen(), aufgabenMailsVerschicken() auf.
	 * Dabei wird versucht den vorgegebenen Zeitpunkt dieser Aktionen möglichst genau einzuhalten.
	 * Hierfür wird das Timerintervall ständig angepasst.
	 * Die Intervalle von lang nach kurz: intervall1= 5 Minuten, intervall2= 30 Sekunden, intervall3= 0,1 Sekunden
	 */
	@Override
	public void actionPerformed(ActionEvent e) {
		if(e.getSource() == timer) {
			// aktuelle Uhrzeit holen
			long jetzt = (new GregorianCalendar()).getTimeInMillis();
			// Aufgaben durchführen
			if(letzterDurchlauf <= stichtag && stichtag < jetzt) { // stichtag < jetzt   ->   neu angelegt Ebenen liegen garantiert im nächsten Stichtag
				stichtag(stichtag, jetzt);
			}
			if(letzterDurchlauf < dbBereinugung && dbBereinugung <= jetzt) {
				dbBereinigen(jetzt);
			}
			if(letzterDurchlauf < aufgabenbenachrichtigung && aufgabenbenachrichtigung <= jetzt) {
				aufgabenMailsVerschicken(jetzt);
			}
			// Zielzeiten aktualisieren
			aktualisiereZielzeiten(jetzt);
			// nächste Zielzeit suchen
			long naechstesZiel  = stichtag;
			if((dbBereinugung < naechstesZiel && dbBereinugung > jetzt) || naechstesZiel < jetzt) {
				naechstesZiel = dbBereinugung;
			}
			if((aufgabenbenachrichtigung < naechstesZiel && aufgabenbenachrichtigung > jetzt) || naechstesZiel < jetzt) {
				naechstesZiel = aufgabenbenachrichtigung;
			}
			// Intervall anpassen
			// ACHTUNG: timer.setDelay scheint das intervall zu setzen nach dem die nächste wartezeit schon begonnen hat
			// 			Es scheint so als ob damit das Delay einen TimerTick zu spät gesetzt wird (entsprechend muss der Delay früher geändert werden)
			if(naechstesZiel-jetzt > 3*intervall1 || naechstesZiel < jetzt) {
				if(timer.getDelay() != intervall1) {
					timer.setDelay(intervall1);
				}
			} else if(naechstesZiel-jetzt > 3*intervall2) {
				if(timer.getDelay() != intervall2) {
					timer.setDelay(intervall2);
				}
			} else if(timer.getDelay() != intervall3) {
				timer.setDelay(intervall3);
			}
			// Zeit der Funktionsausführung merken
			letzterDurchlauf = jetzt;
		}
	}
	
	/**
	 * Diese Funktion aktualisiert die Zeiten für die nächste Datenbankbereinigung, für den nächsten Stichtag und die Benachrichtigung für den kommenden Stichtag.
	 * @param jetzt
	 */
	public void aktualisiereZielzeiten(long jetzt) {
		// Stichtag finden
		try {
			StichtagRepository stichtagRep = StichtagRepository.getInstance();
			Stichtag[] s = stichtagRep.toArray(new Stichtag[0]);
			if(s != null) {
				Stichtag min = null;
				for(int i=0; i<s.length; i++) {
					if(s[i] != null) {
						if(s[i].getStichtag() > jetzt) {
							if(min == null) {
								min = s[i];
							} else if(s[i].getStichtag() < min.getStichtag()) {
								min = s[i];
							}
						}
					}
				}
				if(min != null) {
					stichtag = min.getStichtag();
				}
			}
		} catch (ClassNotFoundException e) {
			
			e.printStackTrace();
		} catch (SQLException e) {
			
			e.printStackTrace();
		}
		
		// Mailzeit finden
		try {
			StichtagRepository stichtagRep = StichtagRepository.getInstance();
			Stichtag[] s = stichtagRep.toArray(new Stichtag[0]);
			if(s != null) {
				Stichtag min = null;
				for(int i=0; i<s.length; i++) {
					if(s[i] != null) {
						if(s[i].getStichtag() > jetzt && s[i].getStatus()==Stichtag.STICHTAGERSTELLT) {
							if(min == null) {
								min = s[i];
							} else if(s[i].getStichtag() < min.getStichtag()) {
								min = s[i];
							}
						}
					}
				}
				if(min != null) {
					if(min.getStichtag()-benachrichtungsPuffer > jetzt) {
						aufgabenbenachrichtigung = min.getStichtag()-benachrichtungsPuffer;
					} else {
						aufgabenbenachrichtigung = jetzt+1;
					}
				}
			}
		} catch (ClassNotFoundException e) {
			
			e.printStackTrace();
		} catch (SQLException e) {
			
			e.printStackTrace();
		}
		
		// Bereinigungszeit finden
		while(dbBereinugung < jetzt) {
			dbBereinugung += dbBerIntervall;
		}
	}
	
	
	
	/**
	 * Diese Funktion erstellt von jeder Ebene im Baum eine neue Version mit dem Zustand Unbearbeitet.
	 * Damit werden die Aufgaben für den nächsten Stichtag eingeleitet.
	 * Diese Funktion wird möglichst kurz nach dem beim Stichtag eingetragenen Zeitpunkt ausgeführt. (Zeitpunkt: long Stichtag.getStichtag(), ab 1 Millisekunde danach Ausführung)
	 * @param stichtag
	 * @param jetzt
	 */
	public void stichtag(long stichtag, long jetzt) {
		System.out.println("Cronjob: Stichtag");
		ArrayList<Ebene> e = new ArrayList<Ebene>(), holUnterebenen = new ArrayList<Ebene>();
		holUnterebenen.add(ebenenRepository.getNeusteEbeneNachId(1));
		// Ebenen holen
		while(holUnterebenen.size() > 0) {
			try {
				if(holUnterebenen.get(0).getUnterelement() != null) {
					for(int i=0; i<holUnterebenen.get(0).getUnterelement().length; i++) {
						holUnterebenen.add(holUnterebenen.get(0).getUnterelement()[i]);
					}
				}
			} catch (ClassNotFoundException e1) {
				
				e1.printStackTrace();
			} catch (SQLException e1) {
				
				e1.printStackTrace();
			}
			e.add(holUnterebenen.get(0));
			holUnterebenen.remove(0);
			holUnterebenen.trimToSize();
		}
		// Stichtag für Ebenen durchführen (neue Versionen anlegen)
		Ebene neu;
		Feld[] f;
		Feld fneu;
		Benutzer[] tmp;
		Ebene[] tmp2;
		Feld[] tmp3;
		String ver, red, uele, felder;
		for(int i=0; i<e.size(); i++) {
			if(e.get(i).getErstellungszeit() < stichtag) { // wenn neueste version vor stichtag erstellt worden ist, neue version erstellen
				try {
					// für neue Ebenenversion Ids in String umwandeln
					ver = "";
					tmp = e.get(i).getVerantwortlich();
					if(tmp != null) {
						for(int j=0; j<tmp.length; j++) {
							ver += tmp[j].getId() + ";";
						}
						if(ver.endsWith(";")) {
							ver = ver.substring(0, ver.length()-1);
						}
					}
					red = "";
					tmp = e.get(i).getRedakteur();
					if(tmp != null) {
						for(int j=0; j<tmp.length; j++) {
							red += tmp[j].getId() + ";";
						}
						if(red.endsWith(";")) {
							red = red.substring(0, red.length()-1);
						}
					}
					uele = "";
					tmp2 = e.get(i).getUnterelement();
					if(tmp2 != null) {
						for(int j=0; j<tmp2.length; j++) {
							uele += tmp2[j].getId() + ";";
						}
						if(uele.endsWith(";")) {
							uele = uele.substring(0, uele.length()-1);
						}
					}
					felder = "";
					tmp3 = e.get(i).getFeld();
					if(tmp3 != null) {
						for(int j=0; j<tmp3.length; j++) {
							felder += tmp3[j].getId() + ";";
						}
						if(felder.endsWith(";")) {
							felder = felder.substring(0, felder.length()-1);
						}
					}
					
					// neue Felderversiionen anlegen
					f = e.get(i).getFeld();
					if(f != null) {
						for(int j=0; j<f.length; j++) {
							fneu = new Feld(f[j].getId(), f[j].getTyp(), f[j].getName(), f[j].isDez2relevant(), jetzt, f[j].getWert());
							felderRepository.saveFeld(fneu);
						}
					}
					// neue Ebenenversion anlegen
					neu = new Ebene(e.get(i).getId(), e.get(i).getName(), e.get(i).getProsaId(), jetzt, Ebene.ZUSTAND_UNBEARBEITET, ver, red, uele, felder);
					ebenenRepository.saveEbene(neu);
				} catch (ClassNotFoundException e1) {
					
					e1.printStackTrace();
				} catch (SQLException e1) {
					
					e1.printStackTrace();
				}
			}
		}
		// Stichtagstatus setzen
		for(int j=0; j<stichtagRepository.size(); j++) {
			if(stichtagRepository.get(j).getStichtag() == stichtag) {
				stichtagRepository.get(j).setStatus(Stichtag.STICHTAGAUSGEFUEHRT);
				try {
					stichtagRepository.saveStichtag(stichtagRepository.get(j));
				} catch (IllegalArgumentException e1) {
					e1.printStackTrace();
				} catch (ClassNotFoundException e1) {
					e1.printStackTrace();
				} catch (SQLException e1) {
					e1.printStackTrace();
				}
			}
		}
	}
	
	/**
	 * Diese Funktion löscht fast alle Ebenenversionen die älter als long loeschAlter (= 2 Jahre) sind.
	 * Ausnahmen sind:
	 * 		- Alle Rootebenenversionen werden nicht gelöscht TODO bessere Möglichkeit?
	 * 		- Alle Ebenenversionen deren Stichtag noch nicht abgelaufen ist.
	 * 		- Für jeden abgelaufenen Stichtag die neuste, von allen akzeptierte Ebenenversion jeder Ebene die an der Root-Ebene des Stichtags hängt,
	 * 			also nicht gelöscht ist, also im Modulhandbuch angezeigt wird.
	 * 		- Für jede Ebene für jeden Redaktionszustand die neuste Ebenenversion,
	 * 			es sei den es gibt von einem höheren Redaktionszustand eine neuere Version (Redakteur_Akzepiert und Dez2_Akzeptiert seien gleichwertig).
	 * 			Dies gilt nicht, wenn die Ebenenversion nicht im Baum des aktuell kommenden Stichtags hängt. (= gelöscht ist).
	 * 		Anmerkung: Diese Ausnahmen können Ebenen doppelt schützen. (z.Bsp. neuste Ebenenversion+Stichtag nicht abgelaufen)
	 * Löschen bedeutet: Entfernen aus den Repositories EbenenRepository und FelderRepository, Löschen aus der SQL-Datenbank
	 * 						und Entfernen aller durch die Vorschau erstellten und zum cachen behaltenen Dateien. (.pdf, .tex, .txt)
	 * Zusätzlich zu den Ebenen werden jedesmal unabhängig vom Dateialter diese Dateien gelöscht: .aux, .log, .out, .synctex.gz, .toc, TabelleXYZ.tex
	 * Diese Funktion wird regelmäßig im Intervall dbBereinigen (= 5 Stunden) ausgeführt.
	 * (Beginnend kurz nach dem Start des Cronjobs)
	 * 
	 * !!!---------------------------------------------------------------------------------
	 * !!! Diese Funktion sollte nur mit vorsicht bearbeitet werden,
	 * !!! da sie die einzige Funktion ist die ernsthaften permanenten Datenverlust herbeiführen kann!
	 * !!!---------------------------------------------------------------------------------
	 * @param jetzt
	 */
	public void dbBereinigen(long jetzt) {
		System.out.println("Cronjob: Bereinigung");
		// zuerst nicht zu cachende Dateien löschen
		nichtZuCachendeDateienLoeschen();
		// ab hier Ebenen und gecacete Dateien löschen
		Ebene[] e = ebenenRepository.toArray(new Ebene[0]);
		Ebene aktuelleRootEbene = null;
		Stichtag letzterStichtag = null;
		ArrayList<Stichtag> abgelaufeneStichtage = new ArrayList<Stichtag>();
		Ebene[] stichtagRootEbene;
		ArrayList<ArrayList<Ebene>> stichtagAngezeigteEbenen = new ArrayList<ArrayList<Ebene>>(); // nicht löschen
		ArrayList<Ebene[]> neusteRedakVersion = new ArrayList<Ebene[]>(); // nicht löschen
		ArrayList<Ebene> loeschKandidaten = new ArrayList<Ebene>(); // löschen???
		ArrayList<Ebene> zuLoeschen = new ArrayList<Ebene>(); // löschen!
		
		Feld[] f;
		Stichtag s;
		boolean inListe, loeschen;
		Ebene[] tmp;
		
		// abgelaufene Stichtage suchen
		for(int i=0; i<stichtagRepository.size(); i++) {
			s = stichtagRepository.get(i);
			if(s.getStichtag() < jetzt) {
				abgelaufeneStichtage.add(s);
				if(letzterStichtag == null) {
					letzterStichtag = s;
				} else if(s.getStichtag() > letzterStichtag.getStichtag()) {
					letzterStichtag = s;
				}
			}
		}
		// rootebenenversionen der stichtage und alt genügende ebenenversion mit abgelaufenem Stichtag suchen
		stichtagRootEbene = new Ebene[abgelaufeneStichtage.size()];
		for(int i=0; i<abgelaufeneStichtage.size(); i++) {
			stichtagAngezeigteEbenen.add(new ArrayList<Ebene>());
		}
		for(int i=0; i<e.length; i++) {
			if(e[i].getId() == 1) { // root ebene
				// aktuelle rootebene suchen
				if(aktuelleRootEbene == null) {
					aktuelleRootEbene = e[i];
				} else if(aktuelleRootEbene.getErstellungszeit() < e[i].getErstellungszeit()) {
					aktuelleRootEbene = e[i];
				}
				// schauen ob root ebene neuste version für einen stichtag ist
				if(e[i].getErstellungszeit() < jetzt) {
					for(int j=0; j<abgelaufeneStichtage.size(); j++) {
						if(abgelaufeneStichtage.get(j).getStichtag() >= e[i].getErstellungszeit()) {
							if(stichtagRootEbene[j] == null) {
								stichtagRootEbene[j] = e[i];
							} else if(stichtagRootEbene[j].getErstellungszeit() < e[i].getErstellungszeit()) {
								stichtagRootEbene[j] = e[i];
							}
						}
					}
				}
			} else { // keine root ebene
				// lösch Alter erreicht und Stichtag abgelaufen?
				if(jetzt - loeschAlter > e[i].getErstellungszeit() && e[i].getErstellungszeit() < letzterStichtag.getStichtag()) {
					loeschKandidaten.add(e[i]);
				}
			}
		}
		// angezeigte ebenenversionen der stichtage und neuste Versionen der Ebenen suchen
		for(int i=0; i<e.length; i++) {
			if(e[i].getId() != 1) { // keine root ebene
				// schauen ob ebene neuste version eines redakzustand ist
				inListe = false;
				for(int j=0; j<neusteRedakVersion.size(); j++) {
					if(neusteRedakVersion.get(j)[neusteRedakVersion.get(j).length-1].getId() == e[i].getId()) {
						inListe = true;
						if(neusteRedakVersion.get(j)[e[i].getRedaktionszustand()] == null) {
							neusteRedakVersion.get(j)[e[i].getRedaktionszustand()] = e[i];
						} else if(neusteRedakVersion.get(j)[e[i].getRedaktionszustand()].getErstellungszeit() < e[i].getErstellungszeit()) {
							neusteRedakVersion.get(j)[e[i].getRedaktionszustand()] = e[i];
						}
						break;
					}
				}
				if(!inListe) {
					tmp = new Ebene[5+1]; // 5 für die redaktionszustände und +1 um ein arrayfeld zu haben das != null ist um die id zu kriegen
					tmp[e[i].getRedaktionszustand()] = e[i];
					tmp[tmp.length-1] = e[i];
					neusteRedakVersion.add(tmp);
				}
				// schauen ob Ebene angezeigte Version von Stichtagen ist
				for(int j=0; j<stichtagAngezeigteEbenen.size(); j++) {
					if(e[i].getErstellungszeit() <= abgelaufeneStichtage.get(j).getStichtag() && e[i].getRedaktionszustand() == Ebene.ZUSTAND_AKZEPTIERT_BEIDE) {
						try {
							if(e[i].haengtAnEbene(stichtagRootEbene[j])) {
								inListe = false;
								for(int k=0; k<stichtagAngezeigteEbenen.get(j).size(); k++) {
									if(stichtagAngezeigteEbenen.get(j).get(k).getId() == e[i].getId()) {
										inListe = true;
										if(stichtagAngezeigteEbenen.get(j).get(k).getErstellungszeit() < e[i].getErstellungszeit()) {
											stichtagAngezeigteEbenen.get(j).set(k, e[i]);
										}
										break;
									}
								}
								if(!inListe) {
									stichtagAngezeigteEbenen.get(j).add(e[i]);
								}
							}
						} catch (ClassNotFoundException e1) {
							
							e1.printStackTrace();
						} catch (SQLException e1) {
							
							e1.printStackTrace();
						}
					}
				}
			}
		}
		// zu löschende Ebenen suchen
		for(int i=0; i<loeschKandidaten.size(); i++) {
			loeschen = true;
			// wird im Modulhandbuch eines Stichtags angezeigt?
			for(int j=0; j<abgelaufeneStichtage.size(); j++) {
				for(int k=0; k<stichtagAngezeigteEbenen.get(j).size(); k++) {
					if(stichtagAngezeigteEbenen.get(j).get(k) == loeschKandidaten.get(i)) {
						loeschen = false;
						break;
					}
				}
			}
			if(loeschen) {
				// ist neuste Version eine Redaktionszustandes, dabei kein neuerer höherer redaktionszustand und hängt an aktueller rootebene?
				for(int j=0; j<neusteRedakVersion.size(); j++) {
					if(neusteRedakVersion.get(j)[neusteRedakVersion.get(j).length-1].getId() == loeschKandidaten.get(i).getId()) {
						if(loeschKandidaten.get(i) == neusteRedakVersion.get(j)[loeschKandidaten.get(i).getRedaktionszustand()]) {
							try {
								if(loeschKandidaten.get(i).haengtAnEbene(aktuelleRootEbene)) {
									switch(loeschKandidaten.get(i).getRedaktionszustand()) {
										case Ebene.ZUSTAND_AKZEPTIERT_BEIDE:
											loeschen = false;
											break;
										case Ebene.ZUSTAND_AKZEPTIERT_DEZ2:
											loeschen = loeschKandidaten.get(i).getErstellungszeit() <= neusteRedakVersion.get(j)[Ebene.ZUSTAND_AKZEPTIERT_BEIDE].getErstellungszeit();
											break;
										case Ebene.ZUSTAND_AKZEPTIERT_REDAKT:
											loeschen = loeschKandidaten.get(i).getErstellungszeit() <= neusteRedakVersion.get(j)[Ebene.ZUSTAND_AKZEPTIERT_BEIDE].getErstellungszeit();
											break;
										case Ebene.ZUSTAND_GEAENDERT:
											loeschen = loeschKandidaten.get(i).getErstellungszeit() <= neusteRedakVersion.get(j)[Ebene.ZUSTAND_AKZEPTIERT_BEIDE].getErstellungszeit() ||
														loeschKandidaten.get(i).getErstellungszeit() <= neusteRedakVersion.get(j)[Ebene.ZUSTAND_AKZEPTIERT_DEZ2].getErstellungszeit() ||
														loeschKandidaten.get(i).getErstellungszeit() <= neusteRedakVersion.get(j)[Ebene.ZUSTAND_AKZEPTIERT_REDAKT].getErstellungszeit();
											break;
										case Ebene.ZUSTAND_UNBEARBEITET:
											loeschen = loeschKandidaten.get(i).getErstellungszeit() <= neusteRedakVersion.get(j)[Ebene.ZUSTAND_AKZEPTIERT_BEIDE].getErstellungszeit() ||
														loeschKandidaten.get(i).getErstellungszeit() <= neusteRedakVersion.get(j)[Ebene.ZUSTAND_AKZEPTIERT_DEZ2].getErstellungszeit() ||
														loeschKandidaten.get(i).getErstellungszeit() <= neusteRedakVersion.get(j)[Ebene.ZUSTAND_AKZEPTIERT_REDAKT].getErstellungszeit() ||
														loeschKandidaten.get(i).getErstellungszeit() <= neusteRedakVersion.get(j)[Ebene.ZUSTAND_GEAENDERT].getErstellungszeit();
											break;
									}
								}
							} catch (ClassNotFoundException e1) {
								loeschen = false; // Sicherheitshalber nicht löschen
								e1.printStackTrace();
								
							} catch (SQLException e1) {
								loeschen = false; // Sicherheitshalber nicht löschen
								e1.printStackTrace();
								
							}
						}
						break;
					}
				}
			}
			if(loeschen) { // ebene soll gelöscht werden
				zuLoeschen.add(loeschKandidaten.get(i));
			}
		}
		
		// löschen der Ebenen und ihrer Felder
		File datei;
		int delFileCount = 0;
		for(int i=0; i<zuLoeschen.size(); i++) {
			// Ebene löschen
			try {
				// 1) Datenbank
				DatenbankSchnittstelle db = DatenbankSchnittstelle.getInstance();
				db.del("ebene", "id="+zuLoeschen.get(i).getId()+" AND erstellungszeit="+zuLoeschen.get(i).getErstellungszeit());
				// 2) Repository
				for(int j=0; j<ebenenRepository.size(); j++) {
					if(ebenenRepository.get(j)==zuLoeschen.get(i)) {
						ebenenRepository.remove(j);
						ebenenRepository.trimToSize();
						break;
					}
				}
				// Felder der Ebene löschen
				f = zuLoeschen.get(i).getFeld();
				if(f != null) {
					for(int j=0; j<f.length; j++) {
						// 1) Datenbank
						db.del("feld", "id="+f[j].getId()+" AND aenderung="+f[j].getAenderung());
						// 2) Repository
						for(int k=0; k<felderRepository.size(); k++) {
							if(felderRepository.get(k)==f[j]) {
								felderRepository.remove(k);
								felderRepository.trimToSize();
								break;
							}
						}
					}
				}
				// von Miktex erstellte Dateien löschen (nur die die fürs cachen gelassen wurden)
				String verzeichnis = "";
				String name = zuLoeschen.get(i).getId() + "_" + zuLoeschen.get(i).getErstellungszeit();
				String[] endungen = {".pdf", ".tex", ".txt"};
				// alle 3 Dateitypen löschen
				for(int j=0; j<endungen.length; j++) {
					datei = new File(verzeichnis+name+endungen[j]);
					if(datei.exists()) {
						try {
							if(!datei.delete()) {
								System.out.println("Cronjob: Datei: "+verzeichnis+name+endungen[j]+" konnte nicht gelöscht werden.");
							} else {
								delFileCount++;
							}
						} catch(SecurityException e1) {
							System.out.println("Cronjob: Datei: "+verzeichnis+name+endungen[j]+" konnte nicht gelöscht werden.");
							e1.printStackTrace();
							
						}
					}
				}
			} catch(ClassNotFoundException e1) {
				e1.printStackTrace();
				
			} catch(SQLException e2) {
				e2.printStackTrace();
				
			}
		}
		if(delFileCount > 0) {
			System.out.println("Cronjob: "+delFileCount+" zu cachende Dateien gelöscht");
		}
	}
	
	/**
	 * Diese Funktion schickt eine Benachrichtigung an alle Modulverantwortlichen, Redakteure und Admins, die noch nicht alle Aufgaben erldeigt haben.
	 * Die Benachrichtigung enthält alle Aufgaben dieser Person und das Datum des kommenden Stichtags.
	 * Diese Funktion wird benachrichtungsPuffer (= 10 Tage) vor dem Stichtag ausgeführt.
	 * Ist ein Stichtag noch nicht vergangen, aber der Zeitpuffer benachrichtungsPuffer bereits abgelaufen, so wird diese Funktion trotzdem ausgeführt.
	 * @param jetzt
	 */
	public void aufgabenMailsVerschicken(long jetzt) {
		System.out.println("Cronjob: Benachrichtigungen verschicken");
		// nächsten unbenachrichtigten Stichtag suchen
		Stichtag min = null;
		try {
			StichtagRepository stichtagRep = StichtagRepository.getInstance();
			Stichtag[] s = stichtagRep.toArray(new Stichtag[0]);
			if(s != null) {
				for(int i=0; i<s.length; i++) {
					if(s[i] != null) {
						if(s[i].getStichtag() > jetzt && s[i].getStatus()==Stichtag.STICHTAGERSTELLT) {
							if(min == null) {
								min = s[i];
							} else if(s[i].getStichtag() < min.getStichtag()) {
								min = s[i];
							}
						}
					}
				}
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
			
		} catch (SQLException e) {
			
			e.printStackTrace();
		}
		if(min != null) {
			// jedem Benutzer seine Mail schicken
			Ebene[] aufgaben, ver, red, dez2;
			for(int i=0; i<benutzerRepository.size(); i++) {
				try {
					ver = ebenenRepository.getVerantwortlichAufgaben(benutzerRepository.get(i), 1);
					red = ebenenRepository.getRedakteurAufgaben(benutzerRepository.get(i), 1);
					dez2 = ebenenRepository.getDezernat2Aufgaben(benutzerRepository.get(i), 1);
					aufgaben = new Ebene[ver.length + red.length + dez2.length];
					for(int j=0; j<ver.length; j++) {
						aufgaben[j] = ver[j];
					}
					for(int j=0; j<red.length; j++) {
						aufgaben[j+ver.length] = red[j];
					}
					for(int j=0; j<dez2.length; j++) {
						aufgaben[j+ver.length+red.length] = dez2[j];
					}
					if(aufgaben.length > 0) {
						Mail.stichtagMail(benutzerRepository.get(i), aufgaben, min.getStichtag());
					}
				} catch (ClassNotFoundException e1) {
					e1.printStackTrace();
					
				} catch (SQLException e1) {
					e1.printStackTrace();
					
				} catch (IOException e) {
					e.printStackTrace();
					
				} catch (Exception e) {
					e.printStackTrace();
					
				}
			}
			try {
				min.setStatus(Stichtag.STICHTAGERINNERT);
				stichtagRepository.saveStichtag(min);
			} catch (SQLException e) {
				e.printStackTrace();
				
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
				
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
				
			}
		}
	}
	
	
	/**
	 * Diese Funktion ist eine Hilfsfunktion für die Datenbankbereinigung.
	 * Sie löscht unabhängig vom Dateialter diese Dateien: .aux, .log, .out, .synctex.gz, .toc, TabelleXYZ.tex
	 */
	private void nichtZuCachendeDateienLoeschen() {
		String[] dateiendungen = {".aux", ".log", ".out", ".synctex.gz", ".toc"};
		String tabelleStart = "Tabelle", tabelleEnde = ".tex";
		// alle dateien im arbeitsverzeichnis holen
		File verzeichnis = new File(".");
		File[] dateien = verzeichnis.listFiles();
		// löschen beginnen
		String name;
		boolean del;
		int delCount = 0;
		for(int i=0; i<dateien.length; i++) {
			del = false;
			// prüfen ob datei vom dateinamen und typ passt
			name = dateien[i].getName();
			if(name.startsWith(tabelleStart) && name.endsWith(tabelleEnde)) {
				del = true;
			} else {
				for(int j=0; j<dateiendungen.length; j++) {
					if(name.endsWith(dateiendungen[j])) {
						del = true;
						break;
					}
				}
			}
			// ggbf. löschen
			if(del) {
				if(dateien[i].exists()) {
					try {
						if(!dateien[i].delete()) {
							System.out.println("Cronjob: Datei: "+name+" konnte nicht gelöscht werden.");
						} else {
							delCount++;
						}
					} catch(SecurityException e1) {
						System.out.println("Cronjob: Datei: "+name+" konnte nicht gelöscht werden.");
						e1.printStackTrace();
						
					}
				}
			}
		}
		if(delCount > 0) {
			System.out.println("Cronjob: "+delCount+" nicht zu cachende Dateien gelöscht");
		}
	}
}
